package com.qiwenshare.file.aop;

import cn.hutool.core.util.ReUtil;
import com.qiwenshare.common.result.RestResult;
import com.qiwenshare.file.constant.UserFileConstant;
import com.qiwenshare.file.domain.AtomicCounter;
import com.qiwenshare.file.dto.file.RenameFileDTO;
import com.qiwenshare.file.util.RedisUtils;
import com.qiwenshare.file.vo.file.FileListVO;
import com.qiwenshare.file.vo.file.SearchFileVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.json.JSONObject;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Date 2024-11-07  05:48
 * @Author libin
 * @Description 后端处理全局页面访问量查询AOP，本系统自增采用前端nginx结合lua访问redis.
 */
@Slf4j
@Component
@Aspect
public class GlobalPageView {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    ThreadLocal<Long> startTime = new ThreadLocal<>();

    ConcurrentHashMap<String, Object> countMap = new ConcurrentHashMap<String, Object>();

    /**
     * 匹配控制层层通知 这里监控controller下的所有接口
     *
     */
    @Pointcut("execution(* com.qiwenshare.file.controller.FileController.incr*(..))")
    private void incrPv() {
    }
    @Pointcut("execution(* com.qiwenshare.file.controller.FileController.renameFile(..))")
    private void incrByHits(){
    }
    @Pointcut("execution(* com.qiwenshare.file.controller.FileController.*FileList(..))")
    private void fetchPv(){
    }
    @Pointcut("execution(* com.qiwenshare.file.controller.FileController.queryFile(..)) || execution(* com.qiwenshare.file.controller.FileController.searchFile(..))")
    private void fetchHits(){

    }
    /**
     * 在接口原有的方法执行前，将会首先执行此处的代码
     *
     */
    @Before("com.qiwenshare.file.aop.GlobalPageView.incrPv()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        startTime.set(System.currentTimeMillis());
        //获取传入目标方法的参数
        Object[] args = joinPoint.getArgs();
    }

    /**
     * 只有正常返回才会执行此方法
     * 如果程序执行失败，则不执行此方法
     *
     */
    @AfterReturning(returning = "returnVal", pointcut = "com.qiwenshare.file.aop.GlobalPageView.incrPv()")
    public void doIncrPv(JoinPoint joinPoint,Object returnVal) throws Throwable {
    //public void doComputePv(ProceedingJoinPoint joinPoint) throws Throwable {
        /*
        Signature signature = joinPoint.getSignature();
        String declaringName = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        String mapKey = declaringName + methodName;
        */
        String mapKey = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        // 执行成功则计数加一
        int increase = AtomicCounter.getInstance().increase();
        RestResult<FileListVO> result = (RestResult<FileListVO>) returnVal;
        List<FileListVO> fileListVOList = result.getDataList();

        synchronized (this) {
            //在项目启动时，需要在Redis中读取原有的PV请求次数 fileUrl
            if (countMap.size() == 0) {
                JSONObject jsonObject = RedisUtils.objFromRedis(UserFileConstant.PAGEVIEW);
                if (jsonObject != null) {
                    Set<String> strings = jsonObject.keySet();
                    for (String string : strings) {
                        Object o = jsonObject.get(string);
                        countMap.putIfAbsent(string, o);
                    }
                }
            }
        }

        // 如果此次访问的接口不在countMap，放入countMap
        countMap.putIfAbsent(mapKey, 0);
        countMap.compute(mapKey, (key, value) -> (Integer) value + 1);

        synchronized (this) {
            // 内存计数达到10 更新redis page:readcount:pv
            if (increase == 10) {
                RedisUtils.objToRedis(UserFileConstant.PAGEVIEW, countMap, 60000);
                //删除过期时间
                stringRedisTemplate.persist(UserFileConstant.PAGEVIEW);
                //计数器置为0
                AtomicCounter.getInstance().toZero();
            }
        }
    }

    @Around("com.qiwenshare.file.aop.GlobalPageView.fetchPv()")
    public Object doFetchPv(ProceedingJoinPoint joinPoint) throws Throwable{
        // 执行前置逻辑
        String pv = stringRedisTemplate.opsForValue().get(UserFileConstant.PAGEVIEW);
        Object proceed = joinPoint.proceed(); // 继续执行被通知的方法
        // 执行后置逻辑
        RestResult<FileListVO> result = (RestResult<FileListVO>) proceed;
        List<FileListVO> fileListVOList = result.getDataList();
        fileListVOList.forEach(item -> {item.setHits(
                Optional.ofNullable(stringRedisTemplate.opsForValue().get(UserFileConstant.ARTICLEVIEW+item.getIdentifier())).orElse("0")
        );item.setPv(Optional.ofNullable(pv).orElse("0"));});
        return proceed;
    }

    @Around("com.qiwenshare.file.aop.GlobalPageView.fetchHits()")
    public Object doFetchHits(ProceedingJoinPoint joinPoint) throws Throwable{
        // 执行前置逻辑
        String pv = stringRedisTemplate.opsForValue().get(UserFileConstant.PAGEVIEW);
        Object proceed = joinPoint.proceed(); // 继续执行被通知的方法
        // 执行后置逻辑
        RestResult<SearchFileVO> result = (RestResult<SearchFileVO>) proceed;
        List<SearchFileVO> fileListVOList = result.getDataList();
        fileListVOList.forEach(item -> {item.setHits(
                Optional.ofNullable(stringRedisTemplate.opsForValue().get(UserFileConstant.ARTICLEVIEW +
                ReUtil.get(UserFileConstant.REGEX_PATTERN,item.getFileUrl(),2))).orElse("0"));
                    item.setPv(Optional.ofNullable(pv).orElse("0"));});
        return proceed;
    }

    @AfterReturning(returning = "returnVal", pointcut = "com.qiwenshare.file.aop.GlobalPageView.incrByHits()")
    public void doIncrByHits(JoinPoint joinPoint,Object returnVal) throws Throwable {
        //MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();//参数值
        // 获取某个参数
        for (Object obj: args) {
            if (obj instanceof RenameFileDTO) {
                RenameFileDTO dto = (RenameFileDTO) obj;
                stringRedisTemplate.opsForValue().set(UserFileConstant.ARTICLEVIEW + dto.getIdentifier(), StringUtils.defaultIfBlank(dto.getHits(),"0"));
                break;
            }
        }
    }

    /**
     * 当接口报错时执行此方法
     */
    @AfterThrowing(pointcut = "com.qiwenshare.file.aop.GlobalPageView.incrPv()")
    public void doIncrThrowing(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.info("接口访问失败，URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get());
    }
    /**
     * 当接口报错时执行此方法
     */
    @AfterThrowing(pointcut = "com.qiwenshare.file.aop.GlobalPageView.incrByHits()")
    public void doIncrByThrowing(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.info("接口访问失败，URI:[{}], 耗费时间:[{}] ms", request.getRequestURI(), System.currentTimeMillis() - startTime.get());
    }
}
